package raft

import (
	"fmt"
	"course/labgob"
)
type RaftLog struct {
	snapLastIdx  int
	snapLastTerm int
	// 创建一个快照 进行阶段 合成一个快照以及剩下的日志
	snapshot	[]byte
	tailLog 	[]LogEntry
}

func NewLog(snapLastIdx, snapLastTerm int, snapshot []byte, entries []LogEntry) * RaftLog {
	rl := &RaftLog{
		snapLastIdx:  snapLastIdx,
		snapLastTerm: snapLastTerm,
		snapshot:     snapshot,
	}
	rl.tailLog = make([]LogEntry, 0 , 1 + len(entries))
	rl.tailLog = append(rl.tailLog, LogEntry{
		Term : snapLastTerm ,
	})
	rl.tailLog = append(rl.tailLog, entries...)
	return rl
}

// 序列化和反序列化

func (rl *RaftLog) readPersist(d *labgob.LabDecoder) error {
	var lastIdx int
	if err := d.Decode(&lastIdx); err != nil {
		return fmt.Errorf("decode last include index failed")
	}
	rl.snapLastIdx = lastIdx

	var lastTerm int
	if err := d.Decode(&lastTerm); err != nil {
		return fmt.Errorf("decode last include term failed")
	}
	rl.snapLastTerm = lastTerm
	
	var log []LogEntry
	if err := d.Decode(&log); err != nil {
			return fmt.Errorf("decode tail log failed")
	}
	rl.tailLog = log

	return nil
}

func (rl *RaftLog) persist(e *labgob.LabEncoder) {
	e.Encode(rl.snapLastIdx)
	e.Encode(rl.snapLastTerm)
	e.Encode(rl.tailLog)
}

// 下标操作
// 实际上的是逻辑下标 也就总长减去分段前面的长度
func (rl *RaftLog) size() int {
	return rl.snapLastIdx + len(rl.tailLog)
}

func (rl *RaftLog) idx(logicIdx int) int {
	println("idx", logicIdx, rl.snapLastIdx, rl.size())
	if logicIdx < rl.snapLastIdx || logicIdx >= rl.size() {
			panic(fmt.Sprintf("%d is out of [%d, %d]", logicIdx, rl.snapLastIdx+1, rl.size()-1))
	}
	return logicIdx - rl.snapLastIdx
}


func (rl *RaftLog) at(logicIdx int) LogEntry {
	return rl.tailLog[rl.idx(logicIdx)]
}

func (rl *RaftLog) firstFor(term int) int {
	for idx, entry := range rl.tailLog {
		if entry.Term == term {
				return idx + rl.snapLastIdx
		} else if entry.Term > term {
				break
		}
	}
	return InvalidIndex
}

func (rl *RaftLog) last() (idx, term int) {
	return rl.size() - 1, rl.tailLog[len(rl.tailLog)-1].Term
}

func (rl *RaftLog) tail(startIdx int) []LogEntry {
	if startIdx >= rl.size() {
			return nil
	}
	return rl.tailLog[rl.idx(startIdx):]
}

func (rl *RaftLog) append(e LogEntry) {
	rl.tailLog = append(rl.tailLog, e)
}

func (rl *RaftLog) appendFrom(prevIdx int, entries []LogEntry) {
	rl.tailLog = append(rl.tailLog[:rl.idx(prevIdx)+1], entries...)
}

func (rl *RaftLog) doSnapshot(index int, snapshot []byte) {
	// since idx() will use rl.snapLastIdx, so we should keep it first
	idx := rl.idx(index)

	rl.snapLastTerm = rl.tailLog[idx].Term
	rl.snapLastIdx = index
	rl.snapshot = snapshot

	// allocate a new slice
	newLog := make([]LogEntry, 0, rl.size()-rl.snapLastIdx)
	newLog = append(newLog, LogEntry{
			Term: rl.snapLastTerm,
	})
	newLog = append(newLog, rl.tailLog[idx+1:]...)
	rl.tailLog = newLog
}

func (rl *RaftLog) installSnapshot(index ,term int, snapshot []byte) {
	// since idx() will use rl.snapLastIdx, so we should keep it first

	rl.snapLastTerm = term
	rl.snapLastIdx = index
	rl.snapshot = snapshot

	// allocate a new slice
	newLog := make([]LogEntry, 0, 1)
	newLog = append(newLog, LogEntry{
			Term: rl.snapLastTerm,
	})
	rl.tailLog = newLog
}